﻿using RoadQualityEvalAPI.Helpers;
using RoadQualityEvalAPI.Models;
using RoadQualityEvaluatorData.Domain.Entities;
using RoadQualityEvaluatorData.DomainServices.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace RoadQualityEvalAPI.Controllers
{
    public class MapSectionController : ApiController
    {

        private MapSectionRepository _mapSectionRepo;

        public List<MapSectionModel> GetAllMapSections()
        {
            _mapSectionRepo = new MapSectionRepository();

            List<MapSectionModel> list = new List<MapSectionModel>();

            foreach (var x in _mapSectionRepo.GetAll())
            {
                MapSectionModel model = new MapSectionModel()
                {
                    Name = x.Name,
                    StreetName = x.StreetName,
                    StartLocation = new LocationModel { Latitude = x.StartLocation.Latitude, Longitude = x.StartLocation.Longitude },
                    EndLocation = new LocationModel { Latitude = x.EndLocation.Latitude, Longitude = x.EndLocation.Longitude },
                    Description = x.Description,
                    OverallQuality = x.OverallQuality,
                    RoughnessQuality = x.RugosityQuality,
                };

                list.Add(model);
            }

            return list;
        }


        public List<LocationModel> GetBestRouteBetweenPoints(double startLat, double startLong, double endLat, double endLong)
        {
            _mapSectionRepo = new MapSectionRepository();

            LocationModel startPoint = new LocationModel() { Latitude = startLat, Longitude = startLong };
            LocationModel endPoint = new LocationModel() { Latitude = endLat, Longitude = endLong };

            // declare a minimum distance variable for each of the 2 points (start and end)
            double minimumDistance_start = 1000;
            double minimumDistance_end = 1000;
            // declare a startVertex and endVertex for each of the 2 location points (start, end) received as parameters from the clients
            Vertex v_startPoint = new Vertex("default_start", 0, 0);
            Vertex v_endPoint = new Vertex("default_end", 0, 0);

            // declare list of vertices and edges
            List<Vertex> vertices = new List<Vertex>();
            List<Edge> edges = new List<Edge>();


            /*
             *  go through all map sections such that:
             *      - we fill up the vertices and edges list which make up our graph
             *      - we find the appropriate (closest) map section points from our starting and ending point (params) such that we can get the appropriate vertices for the closest points
             */
            foreach (var section in _mapSectionRepo.GetAll())
            {
                // define the label of the current vertex
                string label = section.Name.Replace(' ', '_');
                // define the 2 vertices corresponding to the current map section
                Vertex v_start = new Vertex(label + "_start", section.StartLocation.Latitude, section.StartLocation.Longitude);
                Vertex v_end = new Vertex(label + "_end", section.EndLocation.Latitude, section.EndLocation.Longitude);
                // add the 2 vertices in the list

                //TODO: check if v_start and v_end already exist in the database

                if (vertices.Where(v => (v.Latitude.Equals(v_start.Latitude) && v.Longitude.Equals(v_start.Longitude))).FirstOrDefault() == null)
                    vertices.Add(v_start);

                if (vertices.Where(v => (v.Latitude.Equals(v_end.Latitude) && v.Longitude.Equals(v_end.Longitude))).FirstOrDefault() == null)
                    vertices.Add(v_end);
            }

            int countEdges = 0;
            foreach (var section in _mapSectionRepo.GetAll())
            { 
                // add the edge corresponding to the current section to the list
                var v_start = vertices.First(v => (v.Latitude == section.StartLocation.Latitude) && (v.Longitude == section.StartLocation.Longitude));
                var v_end = vertices.First(v => (v.Latitude == section.EndLocation.Latitude) && (v.Longitude == section.EndLocation.Longitude));

                var sectionDistance = GeneralHelpers.ComputeDistanceBetweenTwoLocationPoints(v_start.Latitude, v_start.Longitude, v_end.Latitude, v_end.Longitude);
                
                //var weight = 1/(section.OverallQuality * 0.7 + sectionDistance * 0.3);
                var weight = MapSectionHelper.UpdateDistanceAccordingToQuality(sectionDistance, section.OverallQuality);
                // TODO: add overall quality bias besides distance (such that bigger overall quality mark => smaller distance)


                edges.Add(new Edge("edge_" + countEdges, v_start, v_end, sectionDistance, weight));
                countEdges++;

                // compute distances from first param (startPoint) to start/end locations of the current map section
                double dist1 = GeneralHelpers.ComputeDistanceBetweenTwoLocationPoints(startPoint.Latitude, startPoint.Longitude, section.StartLocation.Latitude, section.StartLocation.Longitude);
                double dist2 = GeneralHelpers.ComputeDistanceBetweenTwoLocationPoints(startPoint.Latitude, startPoint.Longitude, section.EndLocation.Latitude, section.EndLocation.Longitude);

                // find out which one is closest
                if (dist1 <  dist2)
                {
                    if (dist1 < minimumDistance_start)
                    {
                        minimumDistance_start = dist1;
                        v_startPoint = v_start;
                    }
                }
                else
                {
                    if (dist2 < minimumDistance_start)
                    {
                        minimumDistance_start = dist2;
                        v_startPoint = v_end;
                    }
                }

                // same as above, only we do it for the second parameter (endPoint)
                dist1 = GeneralHelpers.ComputeDistanceBetweenTwoLocationPoints(endPoint.Latitude, endPoint.Longitude, section.StartLocation.Latitude, section.StartLocation.Longitude);
                dist2 = GeneralHelpers.ComputeDistanceBetweenTwoLocationPoints(endPoint.Latitude, endPoint.Longitude, section.EndLocation.Latitude, section.EndLocation.Longitude);

                if (dist1 <  dist2)
                {
                    if (dist1 < minimumDistance_end)
                    {
                        minimumDistance_end = dist1;
                        v_endPoint = v_start;
                    }
                }
                else
                {
                    if (dist2 < minimumDistance_end)
                    {
                        minimumDistance_end = dist2;
                        v_endPoint = v_end;
                    }
                }
            }

            // if we are at least 150 m close to one of the start or end points of the sections
            if (minimumDistance_start <= 220 && minimumDistance_end <= 220)
            {

                try
                {

                    // Begin the Dijkstra procedure
                    Dijkstra dijkstra = new Dijkstra(
                        edges, vertices
                    );


                    dijkstra.calculateDistance(v_startPoint);

                    List<LocationModel> path = new List<LocationModel>();
                    
                    // compute and convert the Dijkstra returned path to a list of locations such that the clients can easily interpret the results
                    if (dijkstra.getPathTo(v_endPoint) != null && dijkstra.getPathTo(v_endPoint).Count > 0)
                    {
                        foreach (var x in dijkstra.getPathTo(v_endPoint))
                        {
                            path.Add(new LocationModel() { Latitude = x.Latitude, Longitude = x.Longitude });
                        }
                    }

                    // return the result
                    return path;

                }
                catch (Exception ex)
                {
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);
                }
            }

            // return a NotAcceptable (HttpCode 406) exception if the points were not assigned a vertex on the map sections
            throw new HttpResponseException(HttpStatusCode.NotAcceptable);
        }


        [HttpPost]
        public void UpdateCorrespondingMapSectionsQuality(List<SectionQualityModel> collectedSectionQualityAttributes)
        {
            MapSectionHelper helper = new MapSectionHelper();
            
            if (collectedSectionQualityAttributes != null && collectedSectionQualityAttributes.Count > 0)
            {
                // foreach object in our list, assign location to a map section and update the map section according to the speed and roughness values in the model
                foreach (var qualityAttribute in collectedSectionQualityAttributes)
                {
                    var closestMapSection = helper.AssignLocationToMapSection(new Location() { Latitude = qualityAttribute.Location.Latitude, Longitude = qualityAttribute.Location.Longitude });

                    if (closestMapSection != null)
                    {
                    
                        if (closestMapSection.RugosityQuality == 0)
                        {
                            closestMapSection.RugosityQuality = qualityAttribute.RoughnessValue;
                        }
                        else
                        {
                            closestMapSection.RugosityQuality = closestMapSection.RugosityQuality * 0.7 + qualityAttribute.RoughnessValue * 0.3;
                        }

                        /* FORMULA FOR COMPUTING OVERALL QUALITY (WEIGHT) OF A MAP SECTION
                         * 3 combined factors decide the quality bias: map section roughness value, map section average speed, map section number of potholes & speedbumps
                         */

                        int mapSectionNormalPotholesCount = closestMapSection.Potholes.Where(p => p.Type == 1).Count();
                        int mapSectionBadPotholesCount = closestMapSection.Potholes.Where(p => p.Type == 2).Count();

                        int mapSectionSbCount = closestMapSection.Speedbumps.Count;

                        int speedIndex = MapSectionHelper.ConvertSpeedToIndex(qualityAttribute.SpeedValue);
                        double potholeIndex = MapSectionHelper.ConvertBadPotholesCountToIndex(mapSectionBadPotholesCount) * 0.7 + MapSectionHelper.ConvertNormalPotholesCountToIndex(mapSectionNormalPotholesCount) * 0.3;

                        double qualityValueRough = potholeIndex * 0.5 + closestMapSection.RugosityQuality * 0.3 + speedIndex * 0.2;

                        if (closestMapSection.OverallQuality == 0)
                        {
                            closestMapSection.OverallQuality = qualityValueRough;
                        }
                        else
                        { 
                            closestMapSection.OverallQuality = closestMapSection.OverallQuality * 0.7 + qualityValueRough * 0.3;
                        }

                        helper.MapSectionRepo.Update(closestMapSection);
                    }
                }
            }
        }


        [HttpPost]
        public void AddMapSectionList(List<MapSectionModel> mapSections)
        {
            _mapSectionRepo = new MapSectionRepository();

            List<Pothole> potholes = new List<Pothole>();
            List<Speedbump> speedbumps = new List<Speedbump>();

            if (mapSections != null && mapSections.Count > 0)
            {

                foreach (var mapSection in mapSections)
                {
                    foreach (var p in mapSection.Potholes)
                    {
                        var pot = new Pothole()
                        {
                            CreatedAt = p.CreatedAt,
                            Description = p.Description,
                            Quality = p.Quality,
                            Type = p.Type,
                            Location = new Location { Latitude = p.Location.Latitude, Longitude = p.Location.Longitude }
                        };
                    }

                    foreach (var s in mapSection.Speedbumps)
                    {
                        var sb = new Speedbump()
                        {
                            CreatedAt = s.CreatedAt,
                            Description = s.Description,
                            Location = new Location { Latitude = s.Location.Latitude, Longitude = s.Location.Longitude }
                        };
                    }

                    MapSection x = new MapSection()
                    {
                        Name = mapSection.Name,
                        Description = mapSection.Description,
                        StartLocation = new Location() { Latitude = mapSection.StartLocation.Latitude, Longitude = mapSection.StartLocation.Longitude },
                        EndLocation = new Location() { Latitude = mapSection.EndLocation.Latitude, Longitude = mapSection.EndLocation.Longitude },
                        StreetName = mapSection.StreetName,
                        OverallQuality = mapSection.OverallQuality,
                        RugosityQuality = mapSection.RoughnessQuality,
                        Potholes = potholes,
                        Speedbumps = speedbumps
                    };

                    _mapSectionRepo.Add(x);
                }
            }
        }



    }
}
